package Handlers

import (
	"backend/Service/SQL/MySQL"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/gin-gonic/gin"
	"github.com/gocarina/gocsv"
)

// 用于接收模型服务返回的数据
type price_model_respone struct {
	Ans []float64 `json:"ans"`
	Err string    `json:"err"`
}

// 用于接收price路由获取数据记录时页面上传的数据
type get_lines_upload_data struct {
	Num   *int `json:"num" xml:"num" form:"num" binding:"required"`
	Index *int `json:"index" xml:"index" form:"index" binding:"required"`
}

// 用于接收预测预测集时页面上传的数据
type predict_price_set_upload_data struct {
	Addr       string `json:"addr" xml:"addr" form:"addr" binding:"required"`
	Model_name string `json:"model_name" xml:"model_name" form:"model_name" binding:"required"`
}

// 用于接收预测单个数据时页面上传的数据
type predict_price_single_upload_data struct {
	Addr       string           `json:"addr" xml:"addr" form:"addr" binding:"required"`
	Model_name string           `json:"model_name" xml:"model_name" form:"model_name" binding:"required"`
	Data       MySQL.Price_line `json:"data" xml:"data" form:"data" binding:"required"`
}

// 用于接收数据库groupby的数据
type groupby_data struct {
	Price   *float32 `json:"price" xml:"price" form:"price"`       // 价格字段，使用json、xml和form标签进行命名
	Certain *bool    `json:"certain" xml:"certain" form:"certain"` // 确定性字段，使用json、xml和form标签进行命名
	Column  *int     `json:"column" xml:"column" form:"column"`    // 列字段，使用json、xml和form标签进行命名
}

// 获取最新的记录
func get_price_lines(num, index int) (ans *[]MySQL.Price_line, err error) {
	data := make([]MySQL.Price_line, 0)
	ans = &data
	result := MySQL.DB_conn_for_udcpp.Order("id desc").Offset(num * index).Limit(num).Find(ans)
	err = result.Error
	return
}

// 预测数据，返回预测结果json
func predict(addr, model_name string, data *[]MySQL.Price_line) (ans *[]float64, err error) {
	if len(addr) == 0 || len(model_name) == 0 {
		err = fmt.Errorf("addr and model_name shouldn't be empyt")
		return
	}
	src, err := gocsv.MarshalString(data)
	if err != nil {
		return
	}
	// 发送预测请求
	resp, err := http.PostForm(fmt.Sprintf("http://%s/price/%s", addr, model_name),
		url.Values{"data": {src}},
	)
	if err != nil {
		return
	}
	// 读取返回的数据
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return
	}

	resp_of_model_service := price_model_respone{}
	// json解析返回的数据
	err = json.Unmarshal(body, &resp_of_model_service)
	if err != nil {
		return
	}
	if resp_of_model_service.Err != "" {
		err = fmt.Errorf(resp_of_model_service.Err)
	}
	if resp_of_model_service.Ans != nil {
		ans = &resp_of_model_service.Ans
	}
	return
}

func Price(c *gin.Context) {
	data := get_lines_upload_data{}
	if err := c.ShouldBind(&data); err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	if *data.Num < 0 || *data.Index < 0 {
		c.JSON(http.StatusOK, gin.H{"err": "num and index shouldn't less than 0"})
		return
	}
	ans, err := get_price_lines(*data.Num, *data.Index)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": ans})
}

func PriceAdd(c *gin.Context) {
	data := MySQL.Price_line{}
	if err := c.ShouldBind(&data); err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	// 保存到数据库
	result := MySQL.DB_conn_for_udcpp.Create(&data)
	if result.Error != nil {
		c.JSON(http.StatusOK, gin.H{"err": result.Error.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": "ok"})
}

func PriceUpload(c *gin.Context) {
	// 校验上传的文件
	file, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	if file.Size > FILE_UPLOAD_SIZE {
		c.JSON(http.StatusOK, gin.H{"err": "the size of file is to large!"})
		return
	}
	if file.Filename[len(file.Filename)-4:] != ".csv" {
		c.JSON(http.StatusOK, gin.H{"err": "the file should be end by .csv"})
		return
	}
	// 开始以CSV格式读取
	src, err := file.Open()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	defer src.Close()
	// 获得CSV数据
	lines := []*MySQL.Price_line{}
	err = gocsv.Unmarshal(src, &lines)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	// 根据price是否为0更新Certain字段
	for index := range lines {
		lines[index].Certain = *lines[index].Price != 0
	}
	// 保存数据到数据库
	result := MySQL.DB_conn_for_udcpp.CreateInBatches(&lines, 100)
	if result.Error != nil {
		c.JSON(http.StatusOK, gin.H{"err": result.Error.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": result.RowsAffected})
}

func PriceCharts(c *gin.Context) {
	column := c.DefaultQuery("column", "")
	if len(column) == 0 {
		c.JSON(http.StatusOK, gin.H{"err": "column shouldn't be empty!"})
	}
	data := make([]groupby_data, 0)
	MySQL.DB_conn_for_udcpp.Model(&MySQL.Price_line{}).Group(column + ",certain").Select(fmt.Sprintf("%s as 'column',certain,avg(price) as 'price' ", column)).Order(column + " desc").Scan(&data)
	c.JSON(http.StatusOK, gin.H{"data": data})
}

func PricePredictSingle(c *gin.Context) {
	// 获取必要的参数
	upload_data := predict_price_single_upload_data{}
	if err := c.ShouldBind(&upload_data); err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	// 构建数据
	toPredict := []MySQL.Price_line{upload_data.Data}
	ans, err := predict(upload_data.Addr, upload_data.Model_name, &toPredict)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	if len(*ans) == 1 {
		c.JSON(http.StatusOK, gin.H{"data": (*ans)[0]})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": *ans})

}

func PricePredictSet(c *gin.Context) {
	// 获取必要的参数
	upload_data := predict_price_set_upload_data{}
	if err := c.ShouldBind(&upload_data); err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	// 从数据库中获取预测集
	toPredict := []MySQL.Price_line{}
	result := MySQL.DB_conn_for_udcpp.Where("certain = ?", false).Find(&toPredict)
	if result.Error != nil {
		c.JSON(http.StatusOK, gin.H{"err": result.Error.Error()})
		return
	}
	// 开始预测
	ans, err := predict(upload_data.Addr, upload_data.Model_name, &toPredict)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"err": err.Error()})
		return
	}
	if len(*ans) != len(toPredict) {
		c.JSON(http.StatusOK, gin.H{"err": "the amount of toPredict and predicted is unmatched!"})
		return
	}
	// 更新数据库
	var RowsAffected int64 = 0
	for index := range toPredict {
		*toPredict[index].Price = (*ans)[index]
		result = MySQL.DB_conn_for_udcpp.Save(&toPredict[index])
		RowsAffected += result.RowsAffected
		if result.Error != nil {
			fmt.Println(result.Error.Error())
			continue
		}
	}
	c.JSON(http.StatusOK, gin.H{"data": RowsAffected})
}
